package com.ruoyi.wetest.stats;

import com.codahale.metrics.Snapshot;

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static java.lang.Math.floor;

/**
 * @author Allen
 * @description 压测专家压缩版水池快照版
 * @Date 2024/6/6 14:34
 */
public class WeTestUniformSnapshot extends Snapshot {
    /**
     * 快照压缩值
     */
    private final TreeMap<Long, DataV> values;
    /**
     * 快照实际长度
     */
    private final int realLen;

    public WeTestUniformSnapshot(Map<Long, Integer> values) {
        this.values = new TreeMap<>();
        int realLen = 0;
        for (Map.Entry<Long, Integer> entry : values.entrySet()) {
            this.values.put(entry.getKey(), new DataV(entry.getValue(), 0));

        }
        for (Long key : this.values.keySet()) {
            DataV dataV = this.values.get(key);
            if (dataV != null) {
                //设置该占用的坐标区间
                dataV.setStep(realLen);
                realLen += dataV.count;
            }
        }

        this.realLen = realLen;

    }

    @Override
    public double getValue(double quantile) {
        if (quantile < 0.0 || quantile > 1.0 || Double.isNaN(quantile)) {
            throw new IllegalArgumentException(quantile + " is not in [0..1]");
        }
        if (realLen == 0) {
            return 0.0;
        }
        final double pos = quantile * (realLen + 1);
        final int index = (int) pos;

        if (index < 1) {
            return values.firstKey();
        }

        if (index >= realLen) {
            return values.lastKey();
        }
        Double lower = null;
        Double upper = null;
        for (Map.Entry<Long, DataV> entry : this.values.entrySet()) {
            if (lower == null && entry.getValue().cp(index - 1)) {
                lower = entry.getKey().doubleValue();
            }
            if (upper == null && entry.getValue().cp(index)) {
                upper = entry.getKey().doubleValue();
            }
            if (upper != null && lower != null) break;
        }
        if (lower == null || upper == null) return 0.0;

        return lower + (pos - floor(pos)) * (upper - lower);
    }

    @Override
    public long[] getValues() {
        long[] t = new long[realLen];
        if (realLen == 0) return t;
        int i = 0;
        for (Map.Entry<Long, DataV> entry : this.values.entrySet()) {
            for (int i1 = 0; i1 < entry.getValue().count; i1++) {
                t[i] = entry.getKey();
                i++;
            }
        }
        return t;
    }

    @Override
    public int size() {
        return realLen;
    }

    @Override
    public long getMax() {
        if (realLen == 0) {
            return 0;
        }
        return values.lastKey();
    }

    @Override
    public double getMean() {
        if (realLen == 0) return 0;
        long sum = 0;
        for (Map.Entry<Long, DataV> entry : values.entrySet()) {
            DataV dataV = entry.getValue();
            sum += entry.getKey() * dataV.count;
        }
        return (double) sum / realLen;
    }

    @Override
    public long getMin() {
        if (realLen == 0) {
            return 0;
        }
        return values.firstKey();
    }

    /**
     * 方差
     *
     * @return
     */
    @Override
    public double getStdDev() {
        if (realLen <= 1) {
            return 0;
        }

        final double mean = getMean();
        double sum = 0;

        for (Map.Entry<Long, DataV> entry : values.entrySet()) {
            DataV dataV = entry.getValue();
            long value = entry.getKey() * dataV.count;
            double means = mean * dataV.count;
            final double diff = value - means;
            sum += diff;
        }

        final double variance = sum / (realLen - 1);
        return Math.sqrt(variance);
    }

    @Override
    public void dump(OutputStream output) {
        try (PrintWriter out = new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8))) {
            for (Map.Entry<Long, DataV> entry : values.entrySet()) {
                out.printf("%d,%d%n", entry.getKey(), entry.getValue().count);
            }
        }
    }

    public static class DataV {
        /**
         * 该值出现的次数
         */
        private final int count;
        /**
         * 该值占起始坐标
         */
        private int step;
        /**
         * 该值的结束坐标
         */
        private int endStep;

        public void setStep(int step) {
            this.step = step;
            this.endStep = step + count;
        }

        public DataV(int count, int step) {
            this.count = count;
            this.step = step;
        }

        /**
         * 判断该该值是否处于这个坐标内
         *
         * @param idx
         * @return
         */
        public boolean cp(int idx) {
            return idx < endStep && idx >= step;
        }
    }
}
